<?php

	namespace Aspic\Database;

	use Aspic as A;

	abstract class AbstractDatabase implements QueryExecuterInterface, \Serializable {
		
		const FETCH_TYPE_NUM = 'num';
		const FETCH_TYPE_ASSOC = 'assoc';
		
		/**
		 * When using FETCH_TYPE_CLASS, fetch arg should be the class name or 
		 * an array with first line, the class name, and second, class 
		 * constructor arguments
		 */
		const FETCH_TYPE_CLASS = 'class';
		
		/**
		 * Same as FETCH_TYPE_CLASS except that constructor is called AFTER 
		 * properties have set (not using PDO::FETCH_PROPS_LATE).
		 */
		const FETCH_TYPE_CLASS_CONSTRUCT_LATE = 'classConstructLate';
		
        protected $_dbInfos;
		protected $_queries;
		protected $_pdo;
		protected $_sqlUtil;
		protected $_queryHistoryLength;

		public function __construct() {
			$this->init();
			$this->_queryHistoryLength = 50;
		}
		
		public function init() {
			$this->_dbInfos = array();
			$this->_queries = array();
			
			$utilReflection = new \ReflectionClass(A\Util::getNamespaceName($this).'\\Util');

			$this->_sqlUtil = $utilReflection->newInstance($this);
		}
		
		public function setQueryHistoryLength($length) {
			$this->_queryHistoryLength = $length;
		}
		
		public function resetQueryHistory() {
			$this->_queries = array();
		}
        
		public function getSqlUtil() {
			return $this->_sqlUtil;
		}
		
        abstract protected function initPdo(array $dbInfos);
        
        public function serialize() {
            return serialize($this->_dbInfos);
        }
        
        public function unserialize($dbInfos) {
			$this->init();
            $this->initFromDbInfos($dbInfos);
        }

		protected function throwSqlError($sql, $errorInfos) {
			throw new A\Exception('Query (' . $sql . ') causes an error: {sqlState: ' . $errorInfos[0] . ', code: ' . $errorInfos[1] . ', message: ' . $errorInfos[2] . '}');
		}

		public function getLastQuery() {
			return $this->_queries[count($this->_queries) - 1];
		}

		public function getAllQueries() {
			return $this->_queries;
		}

		public function query($sql) {
			if (count($this->_queries) >= $this->_queryHistoryLength) {
				$this->_queries = array_slice($this->_queries, 1);
			}
			
			$this->_queries[] = $sql;

			$res = $this->_pdo->query($sql);

			if (!$res) {
				$this->throwSqlError($sql, $this->_pdo->errorInfo());
			}

			return $res->rowCount();
		}

		/**
		 * @param array $fetchArgs Array of parameters to give to pdo fetchAll function
		 * @param string $fetchType The structure type of each row in the result return, 
		 * see class constants to know types (FETCH_TYPE_*).
		 */
		public function getResults($sql, $fetchType = self::FETCH_TYPE_CLASS, $fetchTypeArg = 'stdclass') {
			$this->_queries[] = $sql;

			$res = $this->_pdo->query($sql);

			if ($res === false) {
				$this->throwSqlError($sql, $this->_pdo->errorInfo());
			}
			else {
				$fetchArgs = array();
				
				if ($fetchType == self::FETCH_TYPE_ASSOC) {
					$fetchArgs[] = \PDO::FETCH_ASSOC;
				}
				elseif ($fetchType == self::FETCH_TYPE_NUM) {
					$fetchArgs[] = \PDO::FETCH_NUM;
				}
				else {
					if ($fetchType == self::FETCH_TYPE_CLASS) {
						$fetchArgs[] = \PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE;
					}
					elseif ($fetchType == self::FETCH_TYPE_CLASS_CONSTRUCT_LATE) {
						$fetchArgs[] = \PDO::FETCH_CLASS;
					}
					
					$constructorArgs = array();
					
					if (is_array($fetchTypeArg)) {
						$className = $fetchTypeArg[0];
						$constructorArgs = $fetchTypeArg[1];
					}
					else {
						$className = $fetchTypeArg;
					}
					
					if (!class_exists($className)) {
						throw new A\Exception('Class "'.$className.'" does not exist, cannot create row lines using it');
					}
					
					$fetchArgs[] = $className;
					$fetchArgs[] = $constructorArgs;
				}
				
				$data = call_user_func_array(array($res, 'fetchAll'), $fetchArgs);

				if (count($data)) {
					return $data;
				}
				else {
					return null;
				}
			}
		}

		public function getRow($sql, $fetchType = self::FETCH_TYPE_CLASS, $fetchTypeArg = 'stdclass') {
			$res = $this->getResults($sql, $fetchArgs);

			if ($res) {
				return $res[0];
			}
			else {
				return null;
			}
		}

		public function getVar($sql) {
			$fetchArgs = array(
				\PDO::FETCH_COLUMN
			);

			$this->_queries[] = $sql;

			$res = $this->getResults($sql, $fetchArgs);

			if (count($res)) {

				return $res[0][0];
			}
			else {
				return null;
			}
		}

		public function getLinkIdentifier() {
			return $this->_pdo;
		}

		public function getRessource() {
			return $this->_pdo;
		}

		public function getLastInsertedId() {
			return $this->_pdo->lastInsertId();
		}

		public function quote($string) {
			return $this->_pdo->quote($string);
		}
		
		abstract public function fetchTablesNames();
		
		abstract public function fetchTableColumns($tableName);
		
		/**
		 * @return array Array of join array with indexs:
		 * fromColumn, toColumn, fromTable, toTable
		 */
		abstract public function fetchForeignsKeys();
		
		/**
		 * Return an instance of $className in given $dbNamespace folder
		 * @param type $className Name of the class in Database\<db> to instanciate
		 */
		public static function factory($className, $dbNamespace) {
			$classPath = __NAMESPACE__.'\\'.$dbNamespace.'\\'.$className;
			
			if (!class_exists($classPath)) {
				throw new A\Exception('Class '.$className.' does not exist');
			}
			
			$args = array_slice(func_get_args(), 2);
			
			$ref = new \ReflectionClass($classPath);
			$inst = $ref->newInstanceArgs($args);
			
			return $inst;
		}
		

	}

?>